package com.af.security.mgt.account.password;

import com.af.security.auth.user.detail.AfUserDetails;
import com.af.security.mgt.account.password.token.UserPasswordToken;
import com.af.security.mgt.account.password.token.UserPasswordTokenService;
import com.af.security.mgt.user.User;
import com.af.security.mgt.user.UserService;
import com.af.security.property.ProjectProperties;
import com.af.service.AfI18nService;
import com.af.system.util.AfServletUtils;
import com.github.benmanes.caffeine.cache.Cache;
import com.af.system.api.result.AfResult;
import com.af.system.api.result.AfResultUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Locale;

/**
 * @author : zhenyun.su
 * @common: 1. 忘记密码 2. 重置密码， 3. 修改密码
 * @since : 2019/6/25
 */

@Controller
@RequestMapping("account/password")
public class PasswordController {
    @Autowired
    private ProjectProperties projectProperties;
    @Autowired
    private UserPasswordTokenService userPasswordTokenService;
    @Autowired
    private UserService userService;
    @Autowired
    private AfI18nService i18nService;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private com.af.service.AfMailService AfMailService;
    @Autowired
    private Cache<String, AfUserDetails> securityCache;


    private final Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * @comment :给用户邮箱发送，重置密码链接URL
     */
    @GetMapping("forgot")
    public String forgotPage() {
        return "security/account/password/forgot";
    }

    @PostMapping("forgot")
    @ResponseBody
    public AfResult<Object> forgot(String email,
                                 @CookieValue(value = "clientLanguage", required = false) String lang) {
        logger.debug("password forgot: {}", email);
        Locale locale = AfServletUtils.getRequestLocale(lang);
        try {
            User user = userService.findByEmail(email);
            if (user == null) {
                return AfResultUtils.failure(i18nService.getI18n("alert.mail.not.exist", null, locale), null);
            }
            UserPasswordToken token = userPasswordTokenService.createPasswordResetToken(user);
            String queryPath = String.format("account/password/reset?id=%s&token=%s", user.getId(), token.getToken());
            String appUrl = projectProperties.getUrl(queryPath);
            String subject = i18nService.getI18n("password.forgot.mail.subject", null, locale);
            String message = i18nService.getI18n("password.forgot.mail.context", null, locale);
            String text = message + " \n " + appUrl;
            AfMailService.sendTextMail(email, subject, text);
            return AfResultUtils.success();
        } catch (Exception e) {
            logger.error("password reset failed; {}", e);
            String error = i18nService.getI18n("error.password.forgot", null, locale);
            return AfResultUtils.failure(error, null);
        }
    }

    @GetMapping(value = "reset")
    public String resetPage(Model model,
                            @RequestParam("id") long id,
                            @RequestParam("token") String token,
                            @CookieValue(value = "clientLanguage", required = false) String lang) {
        logger.debug("resetPage, according to {}, {}", id, token);
        Locale locale = AfServletUtils.getRequestLocale(lang);
        try {
            UserPasswordToken passToken = userPasswordTokenService.findByToken(token);
            logger.debug("resetPage, get PasswordResetToken; {}", passToken);

            String validString = null;
            if ((passToken == null) || (!passToken.getEnabled()) || (passToken.getUser().getId() != id)) {
                validString = i18nService.getI18n("alert.password.invalidToken", null, locale);
            } else {
                Duration duration = Duration.between(passToken.getExpiryDate(), LocalDateTime.now());
                if (duration.toMillis() >= 1) {
                    validString = i18nService.getI18n("alert.password.expiredToken", null, locale);
                }
            }
            logger.debug("resetPage, set Authority RESET_PASSWORD_PRIVILEGE");
            if (validString != null) {
                SecurityContextHolder.getContext().setAuthentication(null);
            } else {
                validString = i18nService.getI18n("alert.password.token.success", null, locale);
                User user = passToken.getUser();
                model.addAttribute("username", user.getUsername());
                model.addAttribute("token", token);
                model.addAttribute("id", id);
                Authentication auth = new UsernamePasswordAuthenticationToken(
                        user, null, Arrays.asList(
                        new SimpleGrantedAuthority("RESET_PASSWORD_PRIVILEGE")));
                SecurityContextHolder.getContext().setAuthentication(auth);
            }
            model.addAttribute("message", validString);
            return "security/account/password/reset";
        } catch (Exception e) {
            logger.debug("resetPage failed, {}", e);
            model.addAttribute("message", e.getMessage());
            return "security/account/password/reset";
        }
    }

    @PostMapping("reset")
    @ResponseBody
    public AfResult<Object> reset(
            @RequestParam("id") long id,
            @RequestParam("token") String token,
            @RequestParam("password") String password,
            @CookieValue(value = "clientLanguage", required = false) String lang) {
        logger.debug("reset password: {}", password);
        Locale locale = AfServletUtils.getRequestLocale(lang);
        try {
            userPasswordTokenService.disableToken(token);
            User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            user.setPassword(passwordEncoder.encode(password));
            userService.save(user);
            logger.debug("reset password, new password" + user.getPassword());
        } catch (Exception e) {
            logger.error("reset password failed: {}", e.getMessage());
            return AfResultUtils.failure(i18nService.getI18n("error.password.reset", null, locale), "");
        }
        return AfResultUtils.success();
    }

    /**
     * @comment : 登录后，修改密码
     */
    @GetMapping("update")
    public String updatePage() {
        return "security/account/password/update";
    }

    @PostMapping("update")
    @ResponseBody
    public AfResult<Object> update(String oldPassword, String newPassword,
                                 @CookieValue(value = "clientLanguage", required = false) String lang) throws Exception {
        logger.debug("update password: {} to {}", oldPassword, newPassword);
        String message = null;
        Locale locale = AfServletUtils.getRequestLocale(lang);
        try {
            String username = SecurityContextHolder.getContext().getAuthentication().getName();
            User user = userService.findByUsername(username);
            if (passwordEncoder.matches(oldPassword, user.getPassword())) {
                user.setPassword(passwordEncoder.encode(newPassword));
                userService.save(user);
                //删除缓存userdetail数据 - 来自MyUserDetailsService
                securityCache.invalidate(username);

                return AfResultUtils.success();
            } else {
                message = i18nService.getI18n("alert.password.invalid", null, locale);
                return AfResultUtils.failure(message, null);
            }
        } catch (Exception e) {
            logger.error("update password, exception: {}", e.getMessage());
            message = i18nService.getI18n("error.password.update", null, locale);
            return AfResultUtils.failure(message, null);
        }
    }
}
